const NginxConfFile = require('nginx-conf').NginxConfFile;
const GMCONF = require("./gm_conf");
const shell = require('shelljs')
const fs = require('fs');
const SSLCONF = require("./ssl_conf");

let global_nginx_conf;
let vpn_open = null;

let SUPPORT_HTTPS = false;

let LOCAL_SSL_PORT = 443;

console.log(GMCONF);

function createNginxConf(path) {
    console.log("createNginxConf");
    return new Promise((resolve, reject) => {
        NginxConfFile.create(path, function(err, conf) {
            if (err) {
              console.log("nginx conf create failed,", err);
              reject(err);
            } else {
              console.log("nginx conf create success");
              resolve(conf);
            }
        });
    });
}

function readFile(path) {
    return new Promise((resolve, reject) => {
        fs.readFile(path, function(err, data) {
            if(err) {
                console.log("read key file failed:", path);
                reject(err);
            } else {
                resolve(Buffer.from(data||'').toString('hex'));
            }
        });
    });
}

function writeFile(path, data) {
    return new Promise((resolve, reject) => {
        if(fs.existsSync(path)) {
            fs.unlinkSync(path);
        }
        fs.writeFile(path, Buffer.from(data, 'hex'), function(err, data){
            if(err) {
                console.log("write file failed:", path);
                reject(err);
            } else {
                resolve();
            }
        });
    });
}

function waitFlush(nginx_conf) {
    return new Promise((resolve, reject) => {
        nginx_conf.flush((err)=>{
            if(err) {
                reject(err);
            } else {
                resolve(0);
            }
        });
    });
}

function randomRange(min, range) {
    let rand = Math.random();
    return min + Math.round(rand * range);
}

async function vpn_stream_nginx_conf_md(remote_ip, remote_port, local_ip, local_port) {
    if(!global_nginx_conf) {
        return -1;
    }

    let remote_server_val = `${remote_ip}:${remote_port}`;
    let local_server_val = `${local_ip}:${local_port}`;

    global_nginx_conf.nginx.stream.upstream[0].server._value = remote_server_val;
    global_nginx_conf.nginx.stream.upstream[1].server._value = local_server_val;


    return await waitFlush(global_nginx_conf);
}

async function vpn_open_nginx_client_conf_md(client_ssl_cert, client_cert_index, client_ssl_enc, client_enc_cert_index) {
    if(!global_nginx_conf) {
        return -1;
    }
    
    if(!global_nginx_conf.nginx.stream.server[0].proxy_ssl) {
        global_nginx_conf.nginx.stream.server[0]._add("proxy_ssl", "on");
    } else {
       global_nginx_conf.nginx.stream.server[0].proxy_ssl._value = "on";
    }
    
    if(!global_nginx_conf.nginx.stream.server[0].proxy_ssl_gm) {
        global_nginx_conf.nginx.stream.server[0]._add("proxy_ssl_gm", "on");
    } else {
       global_nginx_conf.nginx.stream.server[0].proxy_ssl_gm._value = "on";
    }

    if(!global_nginx_conf.nginx.stream.server[0].proxy_ssl_ciphers) {
        global_nginx_conf.nginx.stream.server[0]._add("proxy_ssl_ciphers", "ECDHE-SM4-SM3");
    } else {
       global_nginx_conf.nginx.stream.server[0].proxy_ssl_ciphers._value = "ECDHE-SM4-SM3";
    }

    if(!global_nginx_conf.nginx.stream.server[0].proxy_ssl_certificate) {
        global_nginx_conf.nginx.stream.server[0]._add("proxy_ssl_certificate", `${client_ssl_cert}`);
    } else {
       global_nginx_conf.nginx.stream.server[0].proxy_ssl_certificate._value = `${client_ssl_cert}`;
    }

    let client_cert_key = `engine:tasscard_sm2:${client_cert_index}`;
    if(!global_nginx_conf.nginx.stream.server[0].proxy_ssl_certificate_key) {
        global_nginx_conf.nginx.stream.server[0]._add("proxy_ssl_certificate_key", client_cert_key);
    } else {
       global_nginx_conf.nginx.stream.server[0].proxy_ssl_certificate_key._value = client_cert_key;
    }

    if(!global_nginx_conf.nginx.stream.server[0].proxy_ssl_enc_certificate) {
        global_nginx_conf.nginx.stream.server[0]._add("proxy_ssl_enc_certificate", `${client_ssl_enc}`);
    } else {
       global_nginx_conf.nginx.stream.server[0].proxy_ssl_enc_certificate._value = `${client_ssl_enc}`;
    }

    let client_enc_cert_key = `engine:tasscard_sm2:${client_enc_cert_index}`;
    if(!global_nginx_conf.nginx.stream.server[0].proxy_ssl_enc_certificate_key) {
        global_nginx_conf.nginx.stream.server[0]._add("proxy_ssl_enc_certificate_key", client_enc_cert_key);
    } else {
       global_nginx_conf.nginx.stream.server[0].proxy_ssl_enc_certificate_key._value = client_enc_cert_key;
    }

    return await waitFlush(global_nginx_conf);
}

async function vpn_open_nginx_server_conf_md(server_ssl_cert, server_cert_index, server_ssl_enc, server_enc_cert_index) {
    if(!global_nginx_conf) {
        return -1;
    }

    if(!global_nginx_conf.nginx.stream.server[1].listen) {
        global_nginx_conf.nginx.stream.server[1]._add("listen", ""+ LOCAL_SSL_PORT + " ssl");
    } else {
        global_nginx_conf.nginx.stream.server[1].listen._value = "" + LOCAL_SSL_PORT + " ssl";
    }
        
    if(!global_nginx_conf.nginx.stream.server[1].ssl_certificate) {
        global_nginx_conf.nginx.stream.server[1]._add("ssl_certificate", `${server_ssl_cert}`);
    } else {
       global_nginx_conf.nginx.stream.server[1].ssl_certificate._value = `${server_ssl_cert}`;
    }

    let server_cert_key = `engine:tasscard_sm2:${server_cert_index}`;
    if(!global_nginx_conf.nginx.stream.server[1].ssl_certificate_key) {
        global_nginx_conf.nginx.stream.server[1]._add("ssl_certificate_key", server_cert_key);
    } else {
       global_nginx_conf.nginx.stream.server[1].ssl_certificate_key._value = server_cert_key;
    }
    if(!global_nginx_conf.nginx.stream.server[1].ssl_enc_certificate) {
        global_nginx_conf.nginx.stream.server[1]._add("ssl_enc_certificate", `${server_ssl_enc}`);
    } else {
       global_nginx_conf.nginx.stream.server[1].ssl_enc_certificate._value = `${server_ssl_enc}`;
    }

    let server_enc_cert_key = `engine:tasscard_sm2:${server_enc_cert_index}`;
    if(!global_nginx_conf.nginx.stream.server[1].ssl_enc_certificate_key) {
        global_nginx_conf.nginx.stream.server[1]._add("ssl_enc_certificate_key", server_enc_cert_key);
    } else {
       global_nginx_conf.nginx.stream.server[1].ssl_enc_certificate_key._value = server_enc_cert_key;
    }

    global_nginx_conf.nginx.stream.server[1]._remove("ssl_session_cache");
    global_nginx_conf.nginx.stream.server[1]._remove("ssl_session_timeout");
    global_nginx_conf.nginx.stream.server[1]._remove("ssl_ciphers");
    global_nginx_conf.nginx.stream.server[1]._remove("ssl_prefer_server_ciphers");

    return await waitFlush(global_nginx_conf);
}

async function vpn_close_nginx_conf_md() {
    if(!global_nginx_conf) {
        return -1;
    }
    global_nginx_conf.nginx.stream.server[0]._remove("proxy_ssl");
    global_nginx_conf.nginx.stream.server[0]._remove("proxy_ssl_gm");
    global_nginx_conf.nginx.stream.server[0]._remove("proxy_ssl_ciphers");
    global_nginx_conf.nginx.stream.server[0]._remove("proxy_ssl_certificate");
    global_nginx_conf.nginx.stream.server[0]._remove("proxy_ssl_certificate_key");
    global_nginx_conf.nginx.stream.server[0]._remove("proxy_ssl_enc_certificate");
    global_nginx_conf.nginx.stream.server[0]._remove("proxy_ssl_enc_certificate_key");

    if(SUPPORT_HTTPS) {
        global_nginx_conf.nginx.stream.server[1].ssl_certificate._value = `/etc/nginx/ssl/server.crt`;
        global_nginx_conf.nginx.stream.server[1].ssl_certificate_key._value = `/etc/nginx/ssl/server.key`;
    
        global_nginx_conf.nginx.stream.server[1]._remove("ssl_enc_certificate");
        global_nginx_conf.nginx.stream.server[1]._remove("ssl_enc_certificate_key");
    
        if(!global_nginx_conf.nginx.stream.server[1].ssl_session_cache) {
            global_nginx_conf.nginx.stream.server[1]._add("ssl_session_cache", "shared:SSL:1m");
        } else {
           global_nginx_conf.nginx.stream.server[1].ssl_session_cache._value = "shared:SSL:1m";
        }
    
        if(!global_nginx_conf.nginx.stream.server[1].ssl_session_timeout) {
            global_nginx_conf.nginx.stream.server[1]._add("ssl_session_timeout", "5m");
        } else {
           global_nginx_conf.nginx.stream.server[1].ssl_session_timeout._value = "5m";
        }
    
        if(!global_nginx_conf.nginx.stream.server[1].ssl_ciphers) {
            global_nginx_conf.nginx.stream.server[1]._add("ssl_ciphers", "HIGH:!aNULL:!MD5");
        } else {
           global_nginx_conf.nginx.stream.server[1].ssl_ciphers._value = "HIGH:!aNULL:!MD5";
        }
    
        if(!global_nginx_conf.nginx.stream.server[1].ssl_prefer_server_ciphers) {
            global_nginx_conf.nginx.stream.server[1]._add("ssl_prefer_server_ciphers", "on");
        } else {
           global_nginx_conf.nginx.stream.server[1].ssl_prefer_server_ciphers._value = "on";
        }
    
        global_nginx_conf.nginx.stream.server[1].listen._value = ""+ LOCAL_SSL_PORT + " ssl";
    } else {
        global_nginx_conf.nginx.stream.server[1]._remove("ssl_certificate");
        global_nginx_conf.nginx.stream.server[1]._remove("ssl_certificate_key");
        global_nginx_conf.nginx.stream.server[1]._remove("ssl_enc_certificate");
        global_nginx_conf.nginx.stream.server[1]._remove("ssl_enc_certificate_key");
        global_nginx_conf.nginx.stream.server[1].listen._value = LOCAL_SSL_PORT;
    }

    return await waitFlush(global_nginx_conf);
}

function nginx_reload() {
    let cmd = `${GMCONF.PATH_NGINX_EXE} -s reload`;
    shell.exec(`sleep 2s`).code;
    let exe_ret = shell.exec(cmd);
    if(exe_ret.code != 0) {
        exe_ret = shell.exec(`${GMCONF.PATH_NGINX_EXE} -c ${GMCONF.PATH_NGINX_CONF}`);
    }
    console.log("nginx reload:",exe_ret);
    if(exe_ret.code != 0) {
        if(exe_ret.stderr && exe_ret.stderr.indexOf("tasscard_sm2:30") != -1) {
            return -2;
        } else if(exe_ret.stderr && exe_ret.stderr.indexOf("tasscard_sm2:20") != -1) {
            return -3;
        }
        return exe_ret.code;
    } else {
        return 0;
    }
}

function openssl_lib_source() {
    console.log("source", GMCONF.PATH_CLIENT_SETTING);
    let cmd_ret = shell.exec(`source ${GMCONF.PATH_CLIENT_SETTING}`);
    return cmd_ret;
}

function openssl_lib_export() {
    let cmd_ret = shell.exec(`export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:${SSLCONF.OPENSSL_LIB}`);
    return cmd_ret;
}

function gen_p10_req(isClient, index, dn, outFile) {
    let cmd = isClient ? GMCONF.PATH_CLIENT_GEN_SH : GMCONF.PATH_SERVER_GEN_SH;
    let exe_cmd = `${cmd} -p -i ${index} -d ${dn} -r ${outFile}`;
    console.log("p10 cmd:", exe_cmd);
    let cmd_ret = shell.exec(exe_cmd).code;
    return cmd_ret;
}

function import_private_key(p7bFile, sign_key_index, enc_key_index) {
    let cmd_ret = shell.exec(`${GMCONF.PATH_TASS_OPENSSL} pkcs7 -in ${p7bFile} -in_sign_key_index ${sign_key_index} -in_enc_key_index ${enc_key_index} -inform GMT009 -engine tasscard_sm2`).code;
    return cmd_ret;
}

function get_vpn_stat() {
    if(vpn_open != null) {
        return vpn_open;
    } else {
        if(!global_nginx_conf) {
            console.log("no global_nginx_conf");
            vpn_open = false;
        }else if(!global_nginx_conf.nginx.stream.server[0].proxy_ssl) {
            console.log("no proxy_ssl");
            vpn_open = false;
        }else if(!global_nginx_conf.nginx.stream.server[0].proxy_ssl_gm) {
            console.log("no proxy_ssl_gm");
            vpn_open = false;
        }else if(!global_nginx_conf.nginx.stream.server[0].proxy_ssl_ciphers) {
            console.log("no proxy_ssl_ciphers");
            vpn_open = false;
        }else if(!global_nginx_conf.nginx.stream.server[0].proxy_ssl_certificate) {
            console.log("no proxy_ssl_certificate");
            vpn_open = false;
        }else if(!global_nginx_conf.nginx.stream.server[0].proxy_ssl_certificate_key) {
            console.log("no proxy_ssl_certificate_key");
            vpn_open = false;
        }else if(!global_nginx_conf.nginx.stream.server[0].proxy_ssl_enc_certificate) {
            console.log("no proxy_ssl_enc_certificate");
            vpn_open = false;
        }else if(!global_nginx_conf.nginx.stream.server[0].proxy_ssl_enc_certificate_key) {
            console.log("no proxy_ssl_enc_certificate_key");
            vpn_open = false;
        }else if(!global_nginx_conf.nginx.stream.server[1].ssl_certificate) {
            console.log("no ssl_certificate");
            vpn_open = false;
        }else if(!global_nginx_conf.nginx.stream.server[1].ssl_certificate_key) {
            console.log("no ssl_certificate_key");
            vpn_open = false;
        }else if(!global_nginx_conf.nginx.stream.server[1].ssl_enc_certificate) {
            console.log("no ssl_enc_certificate");
            vpn_open = false;
        }else if(!global_nginx_conf.nginx.stream.server[1].ssl_enc_certificate_key) {
            console.log("no ssl_enc_certificate_key");
            vpn_open = false;
        }else {
            vpn_open = true;
        }
        return vpn_open;
    }
}

async function lazyNginxConfInit() {
    try{
        global_nginx_conf = await createNginxConf(GMCONF.PATH_NGINX_CONF);
        return 0;
    }catch(exception) {
        return -1;
    }
}

function sign_key(str, private, method, out_sign) {
    let cmd_ret = shell.exec(`echo -n ${str} | ${GMCONF.PATH_TASS_OPENSSL} dgst -sign ${private} -${method} -out ${out_sign}`).code;
    return cmd_ret;
}

function verify_key(str, publickey, method, signfile) {
    //console.log("verify cmd:", `echo -n ${str} | ${GMCONF.PATH_TASS_OPENSSL} dgst -verify ${publickey} -${method} -signature ${signfile}`);
    let cmd_ret = shell.exec(`echo -n ${str} | ${GMCONF.PATH_TASS_OPENSSL} dgst -verify ${publickey} -${method} -signature ${signfile}`);
    if(cmd_ret.code == 0) {
        if(cmd_ret.stdout && cmd_ret.stdout.indexOf("Verified OK") != -1) {
            return true;
        }
    }
    return false;
}

var TassCmd = TassCmd || {};

TassCmd.tass_init = async() => {
    let exe_ret = shell.exec(GMCONF.PATH_TASS_INIT_SH).code;
    if(exe_ret != 0) {
        return -1;
    }
}

TassCmd.open_vpn = async function (remote_ip, remote_port, local_ip, local_port, 
    client_ssl_cert, client_cert_index, client_ssl_enc, client_enc_cert_index,
    server_ssl_cert, server_cert_index, server_ssl_enc, server_enc_cert_index) {
    let ret = 0;
    do{
        ret = await lazyNginxConfInit();
        if(ret != 0) {
            console.log("error lazyNginxConfInit:",ret);
            break;
        }
        ret = await vpn_stream_nginx_conf_md(remote_ip, remote_port, local_ip, local_port);
        if(ret != 0) {
            console.log("error vpn_stream_nginx_conf_md:",ret);
            break;
        }
        ret = await vpn_open_nginx_client_conf_md(client_ssl_cert, client_cert_index, client_ssl_enc, client_enc_cert_index);
        if(ret != 0) {
            console.log("error vpn_open_nginx_client_conf_md:",ret);
            break;
        }
        ret = await vpn_open_nginx_server_conf_md(server_ssl_cert, server_cert_index, server_ssl_enc, server_enc_cert_index);
        if(ret != 0) {
            console.log("error vpn_open_nginx_server_conf_md:",ret);
            break;
        }
        ret = openssl_lib_source();
        if(ret != 0) {
            console.log("error openssl_lib_source:",ret);
            break;
        }
        ret = nginx_reload();
        console.log("nginx_reload:",ret);
        if(ret == 0) {
            vpn_open = true;
        } else {
            vpn_open = false;
        }
    }while(0);
    return ret;
}

TassCmd.shutdown_vpn = async function() {
    let ret = 0;
    do{
        ret = await lazyNginxConfInit();
        if(ret != 0) {
            console.log("error lazyNginxConfInit:",ret);
            break;
        }
        ret = await vpn_close_nginx_conf_md();
        if(ret != 0) {
            console.log("error vpn_close_nginx_conf_md:",ret);
            break;
        }
        ret = openssl_lib_source();
        if(ret != 0) {
            console.log("error openssl_lib_source:",ret);
            break;
        }
        ret = nginx_reload();
        console.log("nginx_reload:",ret);
        if(ret == 0) {
            vpn_open = false;
        }
    }while(0);
    return ret;
}

TassCmd.gen_csr = function(isClient, index, dn, outFile) {
    let ret = 0;
    do{
        ret = openssl_lib_source();
        if(ret != 0) {
            console.log("error openssl_lib_source:",ret);
            break;
        }
        ret = gen_p10_req(isClient, index, dn, outFile);
        console.log("gen_p10_req:",ret);
    }while(0);
    return ret;
}

TassCmd.import_files = async function(isClient, p7bFile, ssl_cert, cert_index, ssl_enc, enc_cert_index) {
    let ret = 0;
    do{
        ret = openssl_lib_source();
        if(ret != 0) {
            console.log("error openssl_lib_source:",ret);
            break;
        }
        ret = import_private_key(p7bFile, cert_index, enc_cert_index);
        if(ret != 0) {
            console.log("error import_private_key:",ret);
            break;
        }
        ret = await lazyNginxConfInit();
        if(ret != 0) {
            console.log("error lazyNginxConfInit:",ret);
            break;
        }
        if(get_vpn_stat()) {
            if(isClient) {
                ret = await vpn_open_nginx_client_conf_md(ssl_cert, cert_index, ssl_enc, enc_cert_index);
            } else {
                ret = await vpn_open_nginx_server_conf_md(ssl_cert, cert_index, ssl_enc, enc_cert_index);
            }
            if(ret != 0) {
                console.log("error vpn_open_nginx_client_conf_md||vpn_open_nginx_server_conf_md:",ret);
                break;
            }
            ret = nginx_reload();
            console.log("nginx_reload:",ret);
        } else {
            console.log("vpn is closed");
        }
    }while(0);
    return ret;
}

TassCmd.vpn_stat = async function() {
    let remoteIp = "";
    let remotePort = "";
    let localIp = "";
    let localPort = "";
    ret = await lazyNginxConfInit();
    if(ret != 0) {
        console.log("error lazyNginxConfInit:",ret);
    } else {
        let val = global_nginx_conf.nginx.stream.upstream[0].server._value;
        remoteIp = val.split(':')[0];
        remotePort = val.split(':')[1];
        val = global_nginx_conf.nginx.stream.upstream[1].server._value;
        localIp = val.split(':')[0];
        localPort = val.split(':')[1];
    }
    return {isOpen: get_vpn_stat(), remoteIp, remotePort, localIp, localPort};
}

TassCmd.doSignature = async function(str) {
    let ret = openssl_lib_export();
    if(ret != 0) {
        return "";
    }
    let path_sign = SSLCONF.PATH_ENC_SIGN + Date.now() + "_" + randomRange(0, 1000) + SSLCONF.SIGN_KEY_FORMAT;
    //console.log("path_sign:", path_sign);
    ret = sign_key(str, SSLCONF.PATH_PRIVATE_KEY, "sm3", path_sign);
    if(ret != 0) {
        return "";
    }
    let data = await readFile(path_sign);
    return (data||"").toString();
}

TassCmd.doVerifySignature = async function(str, sign, publicFile) {
    let ret = openssl_lib_export();
    if(ret != 0) {
        return "";
    }
    let path_sign = SSLCONF.PATH_DEC_SIGN + Date.now() + "_" + randomRange(0, 1000) + SSLCONF.SIGN_KEY_FORMAT;
    //console.log("path_dec_sign:", path_sign);
    await writeFile(path_sign, sign);
    ret = verify_key(str, publicFile, "sm3", path_sign);
    return ret;
}

module.exports = TassCmd;

// async function testNginx() {
//     let ret = await TassCmd.tass_init();
    
//     console.log(global_nginx_conf.nginx.stream);

//     global_nginx_conf.on('flushed', function() {
//         console.log('finished writing to disk');
//     });

//     // vpn_close();

//     // TassCmd.open_vpn("172.20.91.12", 1443, "127.0.0.1", 3000, "/root/tasscard_engine/cert/client/sm2/CS_SM2_CARD.crt", 30,
//     // "/root/tasscard_engine/cert/client/sm2/CE_SM2_CARD.crt", 31, "/root/tasscard_engine/cert/server/sm2/SS_SM2_CARD.crt",
//     // 20, "/root/tasscard_engine/cert/server/sm2/SE_SM2_CARD.crt", 21);

//     console.log(TassCmd.vpn_stat());

// }

// testNginx();

// async function testssl() {
//     let signRet1 = await TassCmd.doSignature("deviceCode=MDAyMQ==&user=abc&token=06862252710dc2243c6057afe2219614");
//     console.log("sign ret:", signRet1);
//     let ret = await TassCmd.doVerifySignature("deviceCode=MDAyMQ==&user=abc&token=06862252710dc2243c6057afe2219614", signRet1, SSLCONF.PATH_PUBLIC_KEY);
//     console.log("verify:",ret);
// }

// testssl();